#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
安全工具函数

这个模块包含应用程序的安全相关功能：
1. 密码加密和验证
2. JWT Token 创建和验证
3. 用户认证和授权
4. 安全中间件支持

使用的安全技术：
- bcrypt: 密码加密
- JWT: 令牌认证
- OAuth2: 认证流程

作者: FastAPI 学习项目
创建时间: 2024
"""

from datetime import datetime, timedelta
from typing import Optional, Union, Any
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from loguru import logger

from config import settings
from app.database.connection import get_db
from app.models.user import User

# 密码加密上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# OAuth2 密码流
oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="/auth/login",
    scheme_name="JWT"
)


def get_password_hash(password: str) -> str:
    """
    生成密码哈希值
    
    使用 bcrypt 算法对密码进行加密
    
    Args:
        password: 原始密码
        
    Returns:
        str: 加密后的密码哈希值
        
    Example:
        ```python
        hashed = get_password_hash("mypassword123")
        print(hashed)  # $2b$12$...
        ```
    """
    try:
        return pwd_context.hash(password)
    except Exception as e:
        logger.error(f"密码加密失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="密码加密失败"
        )


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    验证密码
    
    比较原始密码和哈希密码是否匹配
    
    Args:
        plain_password: 原始密码
        hashed_password: 哈希密码
        
    Returns:
        bool: 密码是否匹配
        
    Example:
        ```python
        is_valid = verify_password("mypassword123", hashed_password)
        if is_valid:
            print("密码正确")
        ```
    """
    try:
        return pwd_context.verify(plain_password, hashed_password)
    except Exception as e:
        logger.error(f"密码验证失败: {e}")
        return False


def create_access_token(
    data: dict, 
    expires_delta: Optional[timedelta] = None
) -> str:
    """
    创建访问令牌
    
    生成 JWT 访问令牌，用于用户认证
    
    Args:
        data: 要编码到令牌中的数据
        expires_delta: 令牌过期时间（可选）
        
    Returns:
        str: JWT 访问令牌
        
    Example:
        ```python
        token = create_access_token(
            data={"sub": "user123"},
            expires_delta=timedelta(minutes=30)
        )
        ```
    """
    try:
        to_encode = data.copy()
        
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(
                minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
            )
        
        to_encode.update({
            "exp": expire,
            "iat": datetime.utcnow(),
            "type": "access"
        })
        
        encoded_jwt = jwt.encode(
            to_encode, 
            settings.SECRET_KEY, 
            algorithm=settings.ALGORITHM
        )
        
        logger.info(f"创建访问令牌成功: {data.get('sub', 'unknown')}")
        return encoded_jwt
        
    except Exception as e:
        logger.error(f"创建访问令牌失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="令牌创建失败"
        )


def create_refresh_token(
    data: dict, 
    expires_delta: Optional[timedelta] = None
) -> str:
    """
    创建刷新令牌
    
    生成 JWT 刷新令牌，用于获取新的访问令牌
    
    Args:
        data: 要编码到令牌中的数据
        expires_delta: 令牌过期时间（可选）
        
    Returns:
        str: JWT 刷新令牌
    """
    try:
        to_encode = data.copy()
        
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(
                days=settings.REFRESH_TOKEN_EXPIRE_DAYS
            )
        
        to_encode.update({
            "exp": expire,
            "iat": datetime.utcnow(),
            "type": "refresh"
        })
        
        encoded_jwt = jwt.encode(
            to_encode, 
            settings.SECRET_KEY, 
            algorithm=settings.ALGORITHM
        )
        
        logger.info(f"创建刷新令牌成功: {data.get('sub', 'unknown')}")
        return encoded_jwt
        
    except Exception as e:
        logger.error(f"创建刷新令牌失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="刷新令牌创建失败"
        )


def verify_token(token: str, token_type: str = "access") -> Optional[dict]:
    """
    验证令牌
    
    解码并验证 JWT 令牌
    
    Args:
        token: JWT 令牌
        token_type: 令牌类型（access 或 refresh）
        
    Returns:
        Optional[dict]: 解码后的令牌数据，验证失败返回 None
        
    Raises:
        HTTPException: 令牌无效时抛出异常
    """
    try:
        payload = jwt.decode(
            token, 
            settings.SECRET_KEY, 
            algorithms=[settings.ALGORITHM]
        )
        
        # 检查令牌类型
        if payload.get("type") != token_type:
            logger.warning(f"令牌类型不匹配: 期望 {token_type}, 实际 {payload.get('type')}")
            return None
        
        # 检查过期时间
        exp = payload.get("exp")
        if exp and datetime.fromtimestamp(exp) < datetime.utcnow():
            logger.warning("令牌已过期")
            return None
        
        return payload
        
    except JWTError as e:
        logger.warning(f"JWT 令牌验证失败: {e}")
        return None
    except Exception as e:
        logger.error(f"令牌验证异常: {e}")
        return None


def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
    """
    获取当前用户
    
    从 JWT 令牌中获取当前登录用户
    
    Args:
        token: JWT 访问令牌
        db: 数据库会话
        
    Returns:
        User: 当前用户对象
        
    Raises:
        HTTPException: 认证失败时抛出异常
        
    Example:
        ```python
        @app.get("/profile")
        async def get_profile(current_user: User = Depends(get_current_user)):
            return current_user
        ```
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        # 验证令牌
        payload = verify_token(token, "access")
        if payload is None:
            raise credentials_exception
        
        # 获取用户 ID
        user_id: str = payload.get("sub")
        if user_id is None:
            raise credentials_exception
        
        # 从数据库获取用户
        from app.database.crud import crud_user
        user = crud_user.get(db, id=int(user_id))
        if user is None:
            raise credentials_exception
        
        return user
        
    except ValueError:
        # 用户 ID 不是有效整数
        raise credentials_exception
    except Exception as e:
        logger.error(f"获取当前用户失败: {e}")
        raise credentials_exception


def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
    """
    获取当前活跃用户
    
    确保当前用户是活跃状态
    
    Args:
        current_user: 当前用户对象
        
    Returns:
        User: 活跃的用户对象
        
    Raises:
        HTTPException: 用户未激活时抛出异常
        
    Example:
        ```python
        @app.get("/protected")
        async def protected_route(user: User = Depends(get_current_active_user)):
            return {"message": f"Hello, {user.username}!"}
        ```
    """
    if not current_user.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, 
            detail="用户账户未激活"
        )
    return current_user


def get_current_superuser(current_user: User = Depends(get_current_active_user)) -> User:
    """
    获取当前超级用户
    
    确保当前用户是超级用户
    
    Args:
        current_user: 当前用户对象
        
    Returns:
        User: 超级用户对象
        
    Raises:
        HTTPException: 用户不是超级用户时抛出异常
    """
    if not current_user.is_superuser:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="权限不足，需要超级用户权限"
        )
    return current_user


def create_token_response(user: User) -> dict:
    """
    创建令牌响应
    
    为用户创建访问令牌和刷新令牌
    
    Args:
        user: 用户对象
        
    Returns:
        dict: 包含令牌信息的字典
        
    Example:
        ```python
        token_data = create_token_response(user)
        # {
        #     "access_token": "eyJ...",
        #     "refresh_token": "eyJ...",
        #     "token_type": "bearer",
        #     "expires_in": 1800
        # }
        ```
    """
    try:
        # 创建访问令牌
        access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
        access_token = create_access_token(
            data={"sub": str(user.id)},
            expires_delta=access_token_expires
        )
        
        # 创建刷新令牌
        refresh_token_expires = timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        refresh_token = create_refresh_token(
            data={"sub": str(user.id)},
            expires_delta=refresh_token_expires
        )
        
        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "bearer",
            "expires_in": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
            "user": {
                "id": user.id,
                "username": user.username,
                "email": user.email,
                "full_name": user.full_name,
                "is_active": user.is_active,
                "is_superuser": user.is_superuser
            }
        }
        
    except Exception as e:
        logger.error(f"创建令牌响应失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="令牌创建失败"
        )


def refresh_access_token(refresh_token: str, db: Session) -> dict:
    """
    刷新访问令牌
    
    使用刷新令牌获取新的访问令牌
    
    Args:
        refresh_token: 刷新令牌
        db: 数据库会话
        
    Returns:
        dict: 新的令牌信息
        
    Raises:
        HTTPException: 刷新失败时抛出异常
    """
    try:
        # 验证刷新令牌
        payload = verify_token(refresh_token, "refresh")
        if payload is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="无效的刷新令牌"
            )
        
        # 获取用户
        user_id = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="无效的令牌数据"
            )
        
        from app.database.crud import crud_user
        user = crud_user.get(db, id=int(user_id))
        if user is None or not user.is_active:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="用户不存在或未激活"
            )
        
        # 创建新的访问令牌
        access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
        new_access_token = create_access_token(
            data={"sub": str(user.id)},
            expires_delta=access_token_expires
        )
        
        return {
            "access_token": new_access_token,
            "token_type": "bearer",
            "expires_in": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
        }
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"刷新访问令牌失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="令牌刷新失败"
        )


def generate_password_reset_token(user_id: int) -> str:
    """
    生成密码重置令牌
    
    Args:
        user_id: 用户 ID
        
    Returns:
        str: 密码重置令牌
    """
    try:
        expires_delta = timedelta(hours=1)  # 1小时有效期
        return create_access_token(
            data={"sub": str(user_id), "type": "password_reset"},
            expires_delta=expires_delta
        )
    except Exception as e:
        logger.error(f"生成密码重置令牌失败: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="密码重置令牌生成失败"
        )


def verify_password_reset_token(token: str) -> Optional[int]:
    """
    验证密码重置令牌
    
    Args:
        token: 密码重置令牌
        
    Returns:
        Optional[int]: 用户 ID，验证失败返回 None
    """
    try:
        payload = verify_token(token, "access")
        if payload is None:
            return None
        
        if payload.get("type") != "password_reset":
            return None
        
        user_id = payload.get("sub")
        return int(user_id) if user_id else None
        
    except (ValueError, TypeError):
        return None
    except Exception as e:
        logger.error(f"验证密码重置令牌失败: {e}")
        return None


# 导出所有组件
__all__ = [
    "get_password_hash",
    "verify_password",
    "create_access_token",
    "create_refresh_token",
    "verify_token",
    "get_current_user",
    "get_current_active_user",
    "get_current_superuser",
    "create_token_response",
    "refresh_access_token",
    "generate_password_reset_token",
    "verify_password_reset_token",
    "oauth2_scheme"
]